package com.hama.iotrust.common.mvc;

import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import org.springframework.context.ApplicationContext;
import org.springframework.context.NoSuchMessageException;

import com.hama.iotrust.common.ValidatorException;
import com.hama.iotrust.common.util.SpringContextHolder;

/**
 * JSR 303 校验工具
 * 
 * @author 金建强(ptma@163.com)
 * @version 1.0.0
 * @since 2013-5-9 11:23:16
 */
public class ValidatorUtil {

    private static final Pattern MESSAGE_PARAMETER_PATTERN = Pattern.compile("(\\{[^\\}]+?\\})");

    /**
     * <p>
     * 根据 JSR 303 检验一个对象
     * </p>
     * <p>
     * 如果未通过校验，所有未通过的校验消息包含在 ValidatorException 异常中抛出
     * </p>
     * 
     * @param obj 待校验的实体对象
     * @throws ValidatorException
     */
    public static void validate(Object obj) throws ValidatorException {
        validateExcludes(obj, new String[] {});
    }

    /**
     * <p>
     * 根据 JSR 303 检验一个对象, 可指定排除校验的属性
     * </p>
     * <p>
     * 如果未通过校验，所有未通过的校验消息包含在 ValidatorException 异常中抛出
     * </p>
     * 
     * @param obj 待校验的实体对象
     * @param excludesProperty 排除校验的对象属性
     * @throws ValidatorException
     */
    public static void validateExcludes(Object obj, String... excludesProperty) throws ValidatorException {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Set<ConstraintViolation<Object>> violations = validator.validate(obj);
        if (violations.size() > 0) {
            StringBuffer validatorMessages = new StringBuffer();
            ApplicationContext context = SpringContextHolder.getApplicationContext();
            for (ConstraintViolation<Object> violation : violations) {
                if (isStringInArray(violation.getPropertyPath().toString(), excludesProperty)) {
                    continue;
                }
                String message;
                try {
                    message = context.getMessage(violation.getMessage(), null, null);
                } catch (NoSuchMessageException nsme) {
                    message = violation.getMessage();
                }
                Map<String, Object> parameters = violation.getConstraintDescriptor().getAttributes();
                validatorMessages.append(replaceAnnotationAttributes(message, parameters)).append("\n");
            }
            if (validatorMessages.length() > 0) throw new ValidatorException(validatorMessages.toString());
        }
    }

    private static boolean isStringInArray(String findString, String[] array) {
        if (array != null && array.length > 0) {
            for (String property : array) {
                if (findString.equals(property)) {
                    return true;
                }
            }
        }
        return false;
    }

    private static String removeCurlyBrace(String parameter) {
        return parameter.substring(1, parameter.length() - 1);
    }

    private static String escapeMetaCharacters(String s) {
        String escapedString = s.replace("\\", "\\\\");
        escapedString = escapedString.replace("$", "\\$");
        return escapedString;
    }

    private static String replaceAnnotationAttributes(String message, Map<String, Object> annotationParameters) {
        Matcher matcher = MESSAGE_PARAMETER_PATTERN.matcher(message);
        StringBuffer sb = new StringBuffer();
        String resolvedParameterValue;
        for (; matcher.find(); matcher.appendReplacement(sb, resolvedParameterValue)) {
            String parameter = matcher.group(1);
            Object variable = annotationParameters.get(removeCurlyBrace(parameter));
            if (variable != null) resolvedParameterValue = escapeMetaCharacters(variable.toString());
            else resolvedParameterValue = parameter;
        }

        matcher.appendTail(sb);
        return sb.toString();
    }

}
